package com.bishe.app.core.utils;


import com.bishe.app.core.common.BizException;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * Created at 2021-09-27
 *
 * @author jeffrey.hu
 */
@Aspect
@Component
public class LockAspect {

    private static final Logger logger = LoggerFactory.getLogger(LockAspect.class);

    private ExpressionParser parser = new SpelExpressionParser();

    private LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();

    @Resource
    private RedisLockManager redisLock;

    @Pointcut("@annotation(com.bishe.app.core.utils.Lock)")
    public void lock() {
    }

    @Around("@annotation(lock)")
    public Object around(ProceedingJoinPoint pjp, Lock lock) throws Throwable {
        if (StringUtils.isEmpty(lock.key()) || StringUtils.isEmpty(lock.lockPrefix())) {
            throw new BizException("@Lock必须指定Key和keyPrefix");
        }

        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
        Method method = methodSignature.getMethod();
        Object[] args = pjp.getArgs();


        String keyValue;
        if (lock.needParseEl()) {
            keyValue = parseSPEL(method, args, lock.key(), String.class, null);
        } else {
            keyValue = lock.key();
        }
        logger.info("执行方法:{},key:{},解析Value:{}", method.getName(), lock.key(), keyValue);
        if (Objects.isNull(keyValue)) {
            throw new BizException("无法解析,key:" + lock.key());
        }

        long start = System.currentTimeMillis();
        // 拼接lockKey
        String lockKey = "@lock" + "_" + lock.lockPrefix() + "_" + keyValue;

        // 上锁失败，直接报错
        String value = UUID.randomUUID().toString() + UUID.randomUUID().toString();
        boolean locked = false;
        try {
            locked = redisLock.lock(lockKey, value, lock.lockSecond(), TimeUnit.SECONDS);
            logger.info("执行方法:{},上锁key:{},上锁结果:{}", method.getName(), lockKey, locked);
            if (!locked) {
                throw new BizException("已经有其他人在操作，请" + lock.lockSecond() + "秒后稍后在尝试");
            }

            // 执行业务方法
            Object result = pjp.proceed();

            logger.info("执行方法:{},执行耗时:{}ms", method.getName(), (System.currentTimeMillis() - start));
            return result;

        } finally {
            if (locked) {
                redisLock.releaseLock(lockKey, value);
            }
        }

    }

    /**
     * 解析EL表达式
     *
     * @param method
     * @param arguments
     * @param spel
     * @param clazz
     * @param defaultResult
     * @param <T>
     * @return
     */
    private <T> T parseSPEL(Method method, Object[] arguments, String spel, Class<T> clazz, T defaultResult) {
        String[] params = discoverer.getParameterNames(method);
        EvaluationContext context = new StandardEvaluationContext();
        for (int len = 0; len < params.length; len++) {
            context.setVariable(params[len], arguments[len]);
        }
        try {
            Expression expression = parser.parseExpression(spel);
            return expression.getValue(context, clazz);
        } catch (Exception e) {
            return defaultResult;
        }
    }

}
